Criterion B Details

# Default ecosystem code for template development.
# This line is replaced by build_ecosystem_pages.py for each ecosystem.
ecosystem_code = 'Zonas Pantanosas Salinas'

Import Python modules.

import os
import yaml
from pathlib import Path
from lonboard import Map
from rle.core import Ecosystems, criterion_b_status, rle_category
from rle.core.eoo import make_eoo
from rle.core.aoo import make_aoo_grid

Load the country config file.

project_root = os.environ.get('PIXI_PROJECT_ROOT', str(Path('..').resolve()))
config_path = Path(project_root) / 'config' / 'country_config.yaml'
with open(config_path) as f:
    config = yaml.safe_load(f)

# Ecosystem index (the COG pixel value for this ecosystem), looked up by code
# from the canonical index table. Absent if the index has not been built yet.
import csv
ecosystem_index = None
index_csv = Path(project_root) / 'config' / 'ecosystems' / 'index.csv'
if index_csv.exists():
    with open(index_csv) as f:
        for row in csv.DictReader(f):
            if row['code'] == ecosystem_code:
                ecosystem_index = int(row['index'])
                break

Load & Filter Ecosystem Data

Load data for all the ecosystems.

import sys
# Shared config helpers live in scripts/; make them importable from here.
sys.path.insert(0, str(Path(project_root) / 'scripts'))
from _config import ensure_vector_source

source = config['ecosystem_source']
# ecosystem_code_column is optional: fall back to the name column.
ecosystem_column = source.get('ecosystem_code_column') or source.get('ecosystem_name_column')
# Prefer the ecosystem-sorted `optimized_data` copy when configured, so that
# filtering to one ecosystem uses parquet predicate pushdown instead of loading
# the whole national map into memory. Falls back to `data`.
ecosystems = Ecosystems.from_file(
    ensure_vector_source(source.get('optimized_data') or source['data']),
    ecosystem_column=ecosystem_column,
    ecosystem_name_column=source.get('ecosystem_name_column'),
    functional_group_column=source.get('functional_group_column')
)

Filter by the Zonas Pantanosas Salinas and check the number of features.

ecosystem = ecosystems.filter(ecosystem_code)
has_data = ecosystem.size() > 0
print(f'{ecosystem.size() = }')
if not has_data:
    from IPython.display import Markdown, display
    display(Markdown(
        f'**No spatial data found for {ecosystem_code}.** '
        f'Criterion B calculations are skipped.'
    ))
ecosystem.size() = 94

Extent of Occurrence (EOO) (subcriterion B1)

Extent of occurrence (EOO). The EOO of an ecosystem is the area (km2) of a minimum convex polygon – the smallest polygon in which no internal angle exceeds 180° that encompasses all known current spatial occurrences of the ecosystem type.

The minimum convex polygon (also known as a convex hull) must not exclude any areas, discontinuities or disjunctions, regardless of whether the ecosystem can occur in those areas or not. Regions such as oceans (for terrestrial ecosystems), land (for coastal or marine ecosystems), or areas outside the study area (such as in a different country) must remain included within the minimum convex polygon to ensure that this standardised method is comparable across ecosystem types. In addition, these features contribute to spreading risks across the distribution of the ecosystem by making different parts of its distribution more spatially independent.

Calculate EOO

Start by calculating the convex hull of the ecosystem’s distribution.

import geopandas as gpd

if has_data:
    ecosystem_geometry = ecosystem.geometry.union_all()
    gdf_ecosystem_polygons = gpd.GeoDataFrame(geometry=[ecosystem_geometry], crs=ecosystem.geometry.crs)
    hull = ecosystem_geometry.convex_hull
    gdf_hull = gpd.GeoDataFrame(geometry=[hull], crs=ecosystem.geometry.crs)

Display the ecosystem’s distribution and the convex hull.

from lonboard import Map, PolygonLayer
from rle.core.viz import smart_map

if has_data:
    eoo_hull = make_eoo(ecosystem).compute()
    display(smart_map([eoo_hull, ecosystem]))
/home/runner/work/rle-tyler-colombia/rle-tyler-colombia/.pixi/envs/default/lib/python3.11/site-packages/lonboard/_geoarrow/ops/reproject.py:116: UserWarning: Input being reprojected to EPSG:4326 CRS.
Lonboard is only able to render data in EPSG:4326 projection.
  warnings.warn(
/home/runner/work/rle-tyler-colombia/rle-tyler-colombia/.pixi/envs/default/lib/python3.11/site-packages/lonboard/_geoarrow/ops/reproject.py:116: UserWarning: Input being reprojected to EPSG:4326 CRS.
Lonboard is only able to render data in EPSG:4326 projection.
  warnings.warn(
if has_data:
    hull_ea = gdf_hull.to_crs("ESRI:54034")
    eoo = hull_ea.geometry.iloc[0].area / 1e6
    print(f'EOO is {eoo:.1f} km2')
EOO is 8354.6 km2

Then calculate the area of the convex hull polygon.

Direct calculation of EOO

EOO can also be calculated directly using …

if has_data:
    ecosystem.eoo

Verify that the area returned by calling make_eoo(ecosystem).compute().area_km2 is the same as the area of the convex hull polygon.

if has_data:
    assert ecosystem.eoo == eoo

Area of Occupancy (AOO) (subcriterion B2)

The protocol for this adjustment includes the following steps:

  1. Intersect AOO grid with the ecosystem’s distribution map.
  2. Calculate extent of the ecosystem type in each grid cell (area) and sum these areas to obtain the total ecosystem area (total area).
  3. Arrange grid cells in ascending order based on their area (smaller first). Calculate accumulated sum of area per cell (cumulative area).
  4. Calculate cumulative proportion by dividing cumulative area by total area (cumulative proportion takes values between 0 and 1)
  5. Calculate AOO by counting the number of cells with a cumulative proportion greater than 0.01 (i.e. exclude cells that in combination account for up to 1% of the total mapped extent of the ecosystem type).

AOO Calculation Details

Intersect AOO grid and ecosystem map

  1. Intersect AOO grid with the ecosystem’s distribution map
from pathlib import Path
from rle.core.aoo import make_aoo_grid_cached

if has_data:
    # Prefer a prebuilt grid cache (e.g. a gs:// URI) so the national AOO grid
    # is not recomputed during CI renders — computing it from the full national
    # ecosystem map peaks at many GB of RAM. Fall back to a local cache when no
    # prebuilt cache is configured. Build one with `pixi run build-caches`.
    cache_path = (
        source.get('aoo_grid_cache_url')
        or (Path(project_root) / '.cache' / 'aoo_grid.parquet')
    )
    aoo_grid = make_aoo_grid_cached(ecosystems, cache_path=cache_path)
    aoo_grid_filtered = aoo_grid.filter_by_ecosystem(ecosystem_code)

Visualize variations in the AOO grid.

from matplotlib.colors import LinearSegmentedColormap
from lonboard.colormap import apply_continuous_cmap
from rle.core.aoo import slugify_ecosystem_name

ecosystem_column = slugify_ecosystem_name(ecosystem_code)
if has_data:
    cmap = LinearSegmentedColormap.from_list("white_red", ["white", "red"])
    values = aoo_grid_filtered.grid_cells[ecosystem_column].values
    normalized = (values - values.min()) / (values.max() - values.min())
    colors = apply_continuous_cmap(normalized, cmap)
    display(smart_map([(aoo_grid_filtered, {"get_fill_color": colors}), ecosystem]))
/home/runner/work/rle-tyler-colombia/rle-tyler-colombia/.pixi/envs/default/lib/python3.11/site-packages/lonboard/_geoarrow/ops/reproject.py:116: UserWarning: Input being reprojected to EPSG:4326 CRS.
Lonboard is only able to render data in EPSG:4326 projection.
  warnings.warn(

Calculate grid cell area and total area

  1. Calculate extent of the ecosystem type in each grid cell (area) and sum these areas to obtain the total ecosystem area (total area).
if has_data:
    keep = ['geometry', 'grid_col', 'grid_row', ecosystem_column]
    gdf = aoo_grid_filtered.grid_cells[keep]
    display(gdf)
geometry grid_col grid_row Zonas_Pantanosas_Salinas
0 POLYGON ((-72.9432 11.46949, -72.9432 11.56174... -813 126 0.011193
1 POLYGON ((-72.85337 11.46949, -72.85337 11.561... -812 126 0.018349
2 POLYGON ((-72.85337 11.56174, -72.85337 11.654... -812 127 0.011454
3 POLYGON ((-72.76354 11.46949, -72.76354 11.561... -811 126 0.005184
4 POLYGON ((-72.76354 11.56174, -72.76354 11.654... -811 127 0.052679
5 POLYGON ((-72.76354 11.65401, -72.76354 11.746... -811 128 0.032612
6 POLYGON ((-72.67371 11.65401, -72.67371 11.746... -810 128 0.059683
7 POLYGON ((-72.58387 11.65401, -72.58387 11.746... -809 128 0.154781
8 POLYGON ((-72.49404 11.65401, -72.49404 11.746... -808 128 0.066528
9 POLYGON ((-72.49404 11.74632, -72.49404 11.838... -808 129 0.014462
10 POLYGON ((-72.40421 11.74632, -72.40421 11.838... -807 129 0.014153
11 POLYGON ((-72.22455 11.83865, -72.22455 11.931... -805 130 0.016290
12 POLYGON ((-72.13472 11.83865, -72.13472 11.931... -804 130 0.048033
13 POLYGON ((-72.13472 11.93102, -72.13472 12.023... -804 131 0.216110
14 POLYGON ((-72.13472 12.02342, -72.13472 12.115... -804 132 0.143729
15 POLYGON ((-72.13472 12.11584, -72.13472 12.208... -804 133 0.000440
16 POLYGON ((-72.13472 12.2083, -72.13472 12.3007... -804 134 0.009282
17 POLYGON ((-72.04489 12.02342, -72.04489 12.115... -803 132 0.054030
18 POLYGON ((-72.04489 12.11584, -72.04489 12.208... -803 133 0.003763
19 POLYGON ((-72.04489 12.2083, -72.04489 12.3007... -803 134 0.001848
20 POLYGON ((-71.95505 12.11584, -71.95505 12.208... -802 133 0.136313
21 POLYGON ((-71.95505 12.2083, -71.95505 12.3007... -802 134 0.006185
22 POLYGON ((-71.86522 12.11584, -71.86522 12.208... -801 133 0.178797
23 POLYGON ((-71.86522 12.2083, -71.86522 12.3007... -801 134 0.028378
24 POLYGON ((-71.86522 12.30079, -71.86522 12.393... -801 135 0.013234
25 POLYGON ((-71.77539 12.11584, -71.77539 12.208... -800 133 0.029527
26 POLYGON ((-71.77539 12.2083, -71.77539 12.3007... -800 134 0.106712
27 POLYGON ((-71.77539 12.30079, -71.77539 12.393... -800 135 0.077685
28 POLYGON ((-71.68556 12.2083, -71.68556 12.3007... -799 134 0.002235
29 POLYGON ((-71.68556 12.30079, -71.68556 12.393... -799 135 0.100297
30 POLYGON ((-71.59573 12.30079, -71.59573 12.393... -798 135 0.051994
31 POLYGON ((-71.59573 12.39331, -71.59573 12.485... -798 136 0.022101
32 POLYGON ((-71.41607 12.39331, -71.41607 12.485... -796 136 0.004097
33 POLYGON ((-71.2364 11.83865, -71.2364 11.93102... -794 130 0.027188

The column Zonas_Pantanosas_Salinas contains the (fractional) area of the ecosystem in each grid cell.

Sum up the areas of each grid cell to get the total area.

if has_data:
    total_area = gdf[ecosystem_column].sum()
    display(total_area)
np.float64(1.7193436720913144)

Calculate cumulative area

  1. Arrange grid cells in ascending order based on their area (smaller first). Calculate accumulated sum of area per cell (cumulative area).
if has_data:
    gdf = gdf.sort_values(by=ecosystem_column)
    gdf["cumulative_area"] = gdf[ecosystem_column].cumsum()
    display(gdf)
geometry grid_col grid_row Zonas_Pantanosas_Salinas cumulative_area
15 POLYGON ((-72.13472 12.11584, -72.13472 12.208... -804 133 0.000440 0.000440
19 POLYGON ((-72.04489 12.2083, -72.04489 12.3007... -803 134 0.001848 0.002288
28 POLYGON ((-71.68556 12.2083, -71.68556 12.3007... -799 134 0.002235 0.004523
18 POLYGON ((-72.04489 12.11584, -72.04489 12.208... -803 133 0.003763 0.008286
32 POLYGON ((-71.41607 12.39331, -71.41607 12.485... -796 136 0.004097 0.012382
3 POLYGON ((-72.76354 11.46949, -72.76354 11.561... -811 126 0.005184 0.017566
21 POLYGON ((-71.95505 12.2083, -71.95505 12.3007... -802 134 0.006185 0.023751
16 POLYGON ((-72.13472 12.2083, -72.13472 12.3007... -804 134 0.009282 0.033033
0 POLYGON ((-72.9432 11.46949, -72.9432 11.56174... -813 126 0.011193 0.044225
2 POLYGON ((-72.85337 11.56174, -72.85337 11.654... -812 127 0.011454 0.055679
24 POLYGON ((-71.86522 12.30079, -71.86522 12.393... -801 135 0.013234 0.068913
10 POLYGON ((-72.40421 11.74632, -72.40421 11.838... -807 129 0.014153 0.083066
9 POLYGON ((-72.49404 11.74632, -72.49404 11.838... -808 129 0.014462 0.097528
11 POLYGON ((-72.22455 11.83865, -72.22455 11.931... -805 130 0.016290 0.113818
1 POLYGON ((-72.85337 11.46949, -72.85337 11.561... -812 126 0.018349 0.132167
31 POLYGON ((-71.59573 12.39331, -71.59573 12.485... -798 136 0.022101 0.154267
33 POLYGON ((-71.2364 11.83865, -71.2364 11.93102... -794 130 0.027188 0.181455
23 POLYGON ((-71.86522 12.2083, -71.86522 12.3007... -801 134 0.028378 0.209833
25 POLYGON ((-71.77539 12.11584, -71.77539 12.208... -800 133 0.029527 0.239360
5 POLYGON ((-72.76354 11.65401, -72.76354 11.746... -811 128 0.032612 0.271972
12 POLYGON ((-72.13472 11.83865, -72.13472 11.931... -804 130 0.048033 0.320006
30 POLYGON ((-71.59573 12.30079, -71.59573 12.393... -798 135 0.051994 0.372000
4 POLYGON ((-72.76354 11.56174, -72.76354 11.654... -811 127 0.052679 0.424679
17 POLYGON ((-72.04489 12.02342, -72.04489 12.115... -803 132 0.054030 0.478709
6 POLYGON ((-72.67371 11.65401, -72.67371 11.746... -810 128 0.059683 0.538392
8 POLYGON ((-72.49404 11.65401, -72.49404 11.746... -808 128 0.066528 0.604920
27 POLYGON ((-71.77539 12.30079, -71.77539 12.393... -800 135 0.077685 0.682604
29 POLYGON ((-71.68556 12.30079, -71.68556 12.393... -799 135 0.100297 0.782901
26 POLYGON ((-71.77539 12.2083, -71.77539 12.3007... -800 134 0.106712 0.889613
20 POLYGON ((-71.95505 12.11584, -71.95505 12.208... -802 133 0.136313 1.025927
14 POLYGON ((-72.13472 12.02342, -72.13472 12.115... -804 132 0.143729 1.169656
7 POLYGON ((-72.58387 11.65401, -72.58387 11.746... -809 128 0.154781 1.324437
22 POLYGON ((-71.86522 12.11584, -71.86522 12.208... -801 133 0.178797 1.503234
13 POLYGON ((-72.13472 11.93102, -72.13472 12.023... -804 131 0.216110 1.719344

Calculate cumulative proportion

  1. Calculate cumulative proportion by dividing cumulative area by total area (cumulative proportion takes values between 0 and 1)
if has_data:
    gdf["cumulative_proportion"] = gdf["cumulative_area"] / total_area
    display(gdf)
geometry grid_col grid_row Zonas_Pantanosas_Salinas cumulative_area cumulative_proportion
15 POLYGON ((-72.13472 12.11584, -72.13472 12.208... -804 133 0.000440 0.000440 0.000256
19 POLYGON ((-72.04489 12.2083, -72.04489 12.3007... -803 134 0.001848 0.002288 0.001331
28 POLYGON ((-71.68556 12.2083, -71.68556 12.3007... -799 134 0.002235 0.004523 0.002631
18 POLYGON ((-72.04489 12.11584, -72.04489 12.208... -803 133 0.003763 0.008286 0.004819
32 POLYGON ((-71.41607 12.39331, -71.41607 12.485... -796 136 0.004097 0.012382 0.007202
3 POLYGON ((-72.76354 11.46949, -72.76354 11.561... -811 126 0.005184 0.017566 0.010217
21 POLYGON ((-71.95505 12.2083, -71.95505 12.3007... -802 134 0.006185 0.023751 0.013814
16 POLYGON ((-72.13472 12.2083, -72.13472 12.3007... -804 134 0.009282 0.033033 0.019212
0 POLYGON ((-72.9432 11.46949, -72.9432 11.56174... -813 126 0.011193 0.044225 0.025722
2 POLYGON ((-72.85337 11.56174, -72.85337 11.654... -812 127 0.011454 0.055679 0.032384
24 POLYGON ((-71.86522 12.30079, -71.86522 12.393... -801 135 0.013234 0.068913 0.040081
10 POLYGON ((-72.40421 11.74632, -72.40421 11.838... -807 129 0.014153 0.083066 0.048313
9 POLYGON ((-72.49404 11.74632, -72.49404 11.838... -808 129 0.014462 0.097528 0.056724
11 POLYGON ((-72.22455 11.83865, -72.22455 11.931... -805 130 0.016290 0.113818 0.066198
1 POLYGON ((-72.85337 11.46949, -72.85337 11.561... -812 126 0.018349 0.132167 0.076870
31 POLYGON ((-71.59573 12.39331, -71.59573 12.485... -798 136 0.022101 0.154267 0.089725
33 POLYGON ((-71.2364 11.83865, -71.2364 11.93102... -794 130 0.027188 0.181455 0.105537
23 POLYGON ((-71.86522 12.2083, -71.86522 12.3007... -801 134 0.028378 0.209833 0.122042
25 POLYGON ((-71.77539 12.11584, -71.77539 12.208... -800 133 0.029527 0.239360 0.139216
5 POLYGON ((-72.76354 11.65401, -72.76354 11.746... -811 128 0.032612 0.271972 0.158184
12 POLYGON ((-72.13472 11.83865, -72.13472 11.931... -804 130 0.048033 0.320006 0.186121
30 POLYGON ((-71.59573 12.30079, -71.59573 12.393... -798 135 0.051994 0.372000 0.216361
4 POLYGON ((-72.76354 11.56174, -72.76354 11.654... -811 127 0.052679 0.424679 0.247001
17 POLYGON ((-72.04489 12.02342, -72.04489 12.115... -803 132 0.054030 0.478709 0.278425
6 POLYGON ((-72.67371 11.65401, -72.67371 11.746... -810 128 0.059683 0.538392 0.313138
8 POLYGON ((-72.49404 11.65401, -72.49404 11.746... -808 128 0.066528 0.604920 0.351832
27 POLYGON ((-71.77539 12.30079, -71.77539 12.393... -800 135 0.077685 0.682604 0.397014
29 POLYGON ((-71.68556 12.30079, -71.68556 12.393... -799 135 0.100297 0.782901 0.455349
26 POLYGON ((-71.77539 12.2083, -71.77539 12.3007... -800 134 0.106712 0.889613 0.517415
20 POLYGON ((-71.95505 12.11584, -71.95505 12.208... -802 133 0.136313 1.025927 0.596697
14 POLYGON ((-72.13472 12.02342, -72.13472 12.115... -804 132 0.143729 1.169656 0.680292
7 POLYGON ((-72.58387 11.65401, -72.58387 11.746... -809 128 0.154781 1.324437 0.770316
22 POLYGON ((-71.86522 12.11584, -71.86522 12.208... -801 133 0.178797 1.503234 0.874307
13 POLYGON ((-72.13472 11.93102, -72.13472 12.023... -804 131 0.216110 1.719344 1.000000

Count AOO cells

  1. Calculate AOO by counting the number of cells with a cumulative proportion greater than 0.01 (i.e. exclude cells that in combination account for up to 1% of the total mapped extent of the ecosystem type).
if has_data:
    aoo = len(gdf[gdf["cumulative_proportion"] > 0.01])
    print(f'AOO is {aoo} cells')
AOO is 29 cells

AOO Calculation (direct call)

if has_data:
    aoo_count = ecosystem.aoo
    print(f'AOO: {aoo_count} grid cells')
AOO: 29 grid cells

Criterion B Summary

Criterion B status (spatial)
Endangered (EN) — Zonas Pantanosas Salinas (Zonas Pantanosas Salinas), index 87

Status reflects the spatial thresholds for EOO (B1) and AOO (B2) only. A final listing under B1/B2 additionally requires at least one of: (a) an observed or inferred continuing decline; (b) threatening processes likely to cause continuing decline within 20 years; or (c) few threat-defined locations — none of which are derived from the spatial metrics.

Sub-criterion Metric Value Category
B1 EOO 8355 km² Endangered (EN)
B2 AOO 29 cells Vulnerable (VU)
Overall B — — Endangered (EN)